package com.jyx.manage_media.service;

import com.alibaba.fastjson.JSON;
import com.jyx.framework.domain.media.MediaFile;
import com.jyx.framework.domain.media.response.CheckChunkResult;
import com.jyx.framework.domain.media.response.MediaCode;
import com.jyx.framework.exception.ExceptionCast;
import com.jyx.framework.model.response.CommonCode;
import com.jyx.framework.model.response.ResponseResult;

import com.jyx.manage_media.config.RabbitMQConfig;
import com.jyx.manage_media.controller.MediaUploadController;
import com.jyx.manage_media.dao.MediaFileRepository;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;

import java.util.*;

@Service
public class MediaUploadService {
    private final static Logger LOGGER  = LoggerFactory.getLogger(MediaUploadController.class);

    @Autowired
    MediaFileRepository mediaFileRepository;
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Value("${jyx-service-manage-media.mq.routingkey-media-video}")
    public String routingKey_media_video;

    @Value("${jyx-service-manage-media.upload-location}")
    String uploadPath;

    /**
     * 根据文件md5得到文件路径
     * 规则：
     * 一级目录：md5的第一个字符
     * 二级目录：md5的第二个字符
     * 三级目录：md5
     * 文件名：md5+文件扩展名
     * @param fileMd5
     * @param fileExt
     * @return
     */
    private String getFilePath(String fileMd5,String fileExt){
        String filePath = uploadPath+fileMd5.substring(0,1)+"/"+fileMd5.substring(1,2)+"/"+fileMd5+"/"+fileMd5+"."+fileExt;
        return filePath;
    }

    /**
     * 获取媒体文件的相对路径
     * @param fileMd5
     * @param fileExt
     * @return
     */
    private String getFileFolderRelativePath(String fileMd5,String fileExt){
        String fileRelativePath = fileMd5.substring(0,1)+"/"+fileMd5.substring(1,2)+"/"+fileMd5+"/";
        return fileRelativePath;
    }

    /**
     * 得到文件的目录
     * @param fileMd5
     * @return
     */
    private String getFileFolderPath(String fileMd5){
        String fileFolderPath = uploadPath+fileMd5.substring(0,1)+"/"+fileMd5.substring(1,2)+"/"+fileMd5+"/";
        return fileFolderPath;
    }

    /**
     * 创建文件目录
     * @param fileMd5
     * @return
     */
    private boolean createFileFolder(String fileMd5){
        String fileFolderPath = getFileFolderPath(fileMd5);
        File file = new File(fileFolderPath);
        if(!file.exists()){
            boolean mkdirs = file.mkdirs();
            return mkdirs;
        }
        return true;
    }

    /**
     * 文件上传注册(检验文件是否存在)
     * @param fileMd5
     * @param fileName
     * @param fileSize
     * @param mimetype
     * @param fileExt
     * @return
     */
    public ResponseResult register(String fileMd5,String fileName,Long fileSize,String mimetype,String fileExt){
        //检查文件是否上传
        String filePath = getFilePath(fileMd5, fileExt);
        File file = new File(filePath);
        //文件存在则返回已存在
        Optional<MediaFile> mediaFileOptional = mediaFileRepository.findById(fileMd5);
        if(file.exists()&&mediaFileOptional.isPresent()){
            return new ResponseResult(MediaCode.UPLOAD_FILE_REGISTER_EXIST);
        }
        boolean fileFolder = createFileFolder(fileMd5);
        if(!fileFolder){
            //上传文件目录失败
            ExceptionCast.cast(MediaCode.UPLOAD_FILE_REGISTER_FAIL);
        }
        return new ResponseResult(CommonCode.SUCCESS);
    }

    /**
     * 获取文件块路径
     * @param fileMd5
     * @return
     */
    public String getChunkFileFolderPath(String fileMd5){
        String fileFolderChunksPath = getFileFolderPath(fileMd5) + "/" + "chunks" + "/";
        File folderChunksPath = new File(fileFolderChunksPath);
        if(!folderChunksPath.exists()){
            folderChunksPath.mkdirs();
        }
        return fileFolderChunksPath;
    }

    //检查分块
    public CheckChunkResult checkChunks(String fileMd5,Integer chunk,Integer chunkSize){
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        File chunkFile = new File(chunkFileFolderPath+chunk);
        if(chunkFile.exists()){
            return new CheckChunkResult(MediaCode.CHUNK_FILE_EXIST_CHECK,true);
        }else {
            return new CheckChunkResult(MediaCode.CHUNK_FILE_NOT_EXIST_CHECK,false);
        }

    }

    /**
     * 块文件上传
     * @param file
     * @param fileMd5
     * @param chunk
     * @return
     */
    public ResponseResult uploadChunks(MultipartFile file,String fileMd5,Integer chunk){
        //
        if(file == null){
            ExceptionCast.cast(MediaCode.UPLOAD_FILE_REGISTER_ISNULL);
        }
        boolean fileFolder = createFileFolder(fileMd5);
        File fileChunk = new File(getChunkFileFolderPath(fileMd5) + chunk);
        if(!fileChunk.exists()){
            try {
                fileChunk.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //创建流
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try{
            inputStream = file.getInputStream();
            outputStream = new FileOutputStream(fileChunk);
            IOUtils.copy(inputStream, outputStream);
        }catch (Exception e){
            e.printStackTrace();
            LOGGER.error("upload chunk file fail:{}",e.getMessage());
        }finally {
            try{
                inputStream.close();
            }catch (IOException e){
                e.printStackTrace();
            }
            try {
                outputStream.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
        return new ResponseResult(CommonCode.SUCCESS);
    }

    public ResponseResult sendProcessVideoMsg(String mediaId){
        Optional<MediaFile> mediaFileOptional = mediaFileRepository.findById(mediaId);
        if(!mediaFileOptional.isPresent()){
            return new ResponseResult(MediaCode.UPLOAD_FILE_REGISTER_ISNULL);
        }
        MediaFile mediaFile = mediaFileOptional.get();
        Map<String, String> msgMap = new HashMap<>();
        msgMap.put("mediaId", mediaId);
        String msg = JSON.toJSONString(msgMap);
        try{
            rabbitTemplate.convertAndSend(RabbitMQConfig.EX_MEDIA_PROCESSTASK,routingKey_media_video,msg);
        }catch (Exception e){
            e.printStackTrace();
            LOGGER.error("Send rabbit mq fail,msg is :{}",e.getMessage());
        }
        return new ResponseResult(CommonCode.SUCCESS);
    }


    /**
     * 创建块文件路径
     * @param fileMd5
     * @return
     */
    private boolean createChunkFileFolder(String fileMd5){
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        File chunkFileFolder = new File(chunkFileFolderPath);
        if(!chunkFileFolder.exists()){
            boolean mkdirs = chunkFileFolder.mkdirs();
            return mkdirs;
        }
        return true;
    }

    /**
     * 合并文件
     * @param fileMd5
     * @param fileName
     * @param fileSize
     * @param mimeType
     * @param fileExt
     * @return
     */
    public ResponseResult mergeChunks(String fileMd5,String fileName,Long fileSize,String mimeType,String fileExt){
        //获取块路径
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        File chunkFileFolder = new File(chunkFileFolderPath);
        if(!chunkFileFolder.exists()){
            chunkFileFolder.mkdirs();
        }
        File mergeFile = new File(getFilePath(fileMd5, fileExt));
        if(mergeFile.exists()){
            mergeFile.delete();
        }
        boolean newFile =false;
        try{
            newFile = mergeFile.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
            LOGGER.error("mergechunks..create mergeFile fail:{}",e.getMessage());
        }
        if(!newFile){
            ExceptionCast.cast(MediaCode.MERGE_FILE_FAIL);
        }
        List<File> chunkFiles = getChunkFiles(chunkFileFolder);

        //合并文件
        mergeFile = mergeFile(mergeFile, chunkFiles);

        if(mergeFile == null){
            ExceptionCast.cast(MediaCode.MERGE_FILE_FAIL);
        }
        //校验md5
        boolean checkFileMd5 = checkFileMd5(mergeFile, fileMd5);
        if(!checkFileMd5){
            ExceptionCast.cast(MediaCode.MERGE_FILE_CHECKFAIL);
        }
        //保存文件信息入MongoDB
        MediaFile mediaFile = new MediaFile();
        mediaFile.setId(fileMd5);
        mediaFile.setFileName(fileMd5+"."+fileExt);
        mediaFile.setFileOriginalName(fileName);
        mediaFile.setFilePath(getFileFolderRelativePath(fileMd5,fileExt));
        mediaFile.setFileSize(fileSize);
        mediaFile.setUploadTime(new Date());
        mediaFile.setMimeType(mimeType);
        mediaFile.setFileType(fileExt);
        mediaFile.setFileStatus("301002");
        //mediaFileRepository.save(mediaFile);
        //返回结果
        //sendProcessVideoMsg(mediaFile.getFileId());
        Map<String, String> msgMap = new HashMap<>();
        msgMap.put("mediaId",mediaFile.getId());
        String msg = JSON.toJSONString(msgMap);
        try{
            rabbitTemplate.convertAndSend(RabbitMQConfig.EX_MEDIA_PROCESSTASK,routingKey_media_video,msg);
            LOGGER.info("Send rabbit mq success ,msg is :{}",msg);
        }catch (Exception e){
            e.printStackTrace();
            LOGGER.error("Send rabbit mq fail,msg is :{}",e.getMessage());
        }
        mediaFileRepository.save(mediaFile);
        return new ResponseResult(CommonCode.SUCCESS);
    }

    /**
     * 检查文件MD5
     * @param mergeFile
     * @param fileMd5
     * @return
     */
    private boolean checkFileMd5(File mergeFile,String fileMd5){
        if(mergeFile == null || fileMd5 == null){
            return false;
        }
        FileInputStream mergeFileInputStream = null;
        try {
            mergeFileInputStream = new FileInputStream(mergeFile);
            String mergeFileMd5 = DigestUtils.md5Hex(mergeFileInputStream);
            if(fileMd5.equalsIgnoreCase(mergeFileMd5)){
                return true;
            }
        }catch (Exception e){
            e.printStackTrace();
            LOGGER.error("checkMd5 fail,file is:{},md5 is:{}",mergeFile.getAbsoluteFile(),fileMd5);
        }finally {
            try {
                mergeFileInputStream.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     * 获取块文件列
     * @param chunkFileFolder
     * @return
     */
    private List<File> getChunkFiles(File chunkFileFolder){
        /**
         * 获取块文件
         */
        File[] chunkFiles = chunkFileFolder.listFiles();
        List<File> chunkFileList = new ArrayList<>();
        chunkFileList.addAll(Arrays.asList(chunkFiles));
        Collections.sort(chunkFileList, new Comparator<File>() {
            @Override
            public int compare(File o1, File o2) {
                if(Integer.parseInt(o1.getName())>Integer.parseInt(o2.getName())){
                    return 1;
                }
                return -1;
            }
        });
        return chunkFileList;
    }

    /**
     * 合并文件
     * @param mergeFile
     * @param chunkFiles
     * @return
     */
    private File mergeFile(File mergeFile,List<File> chunkFiles){
        try{
            RandomAccessFile raf_write = new RandomAccessFile(mergeFile, "rw");
            byte[] b = new byte[1024];
            for (File fileChunk: chunkFiles) {
                RandomAccessFile raf_read = new RandomAccessFile(fileChunk, "r");
                int len = -1;
                while((len=raf_read.read(b))!=-1){
                    raf_write.write(b,0,len);
                }
                raf_read.close();
            }
            raf_write.close();
            return mergeFile;
        }catch (Exception e){
            e.printStackTrace();
            LOGGER.error("merge file error:{}",e.getMessage());
            return null;
        }
    }
}
